package com.naiterui.ehp.bs.esign.api.scca.util;/**
 * @author guoyongxiang
 * @date 2021/2/23 17:17
 * @since 1.0.0
 */

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.collect.Maps;
import com.naiterui.ehp.bp.support.exception.BusinessException;
import com.naiterui.ehp.bp.utils.lang.ConfigUtil;
import com.naiterui.ehp.bp.utils.net.RestHttpUtil;
import com.naiterui.ehp.bs.esign.api.scca.vo.GenerateQRCodeVO;
import com.naiterui.ehp.bs.esign.api.scca.vo.SccaCertInfoVO;
import com.naiterui.ehp.bs.esign.api.scca.vo.SccaCertSignVO;
import com.naiterui.ehp.bs.esign.api.scca.vo.SccaCertVO;
import com.naiterui.ehp.bs.esign.exception.ExceptionCodes;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.HmacAlgorithms;
import org.apache.commons.codec.digest.HmacUtils;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.ResponseEntity;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

/**
 * 四川CA接口操作工具类
 *
 * @author guoyongxiang
 * @version 1.0.0
 * @date 2021/2/23 17:17
 **/
@Slf4j
public class SignSccaUtil {

    public static final String APP_ID = ConfigUtil.getString("esign.scca.appId");
    private static final String APP_SECRET = ConfigUtil.getString("esign.scca.appSecret");
    private static final String API_HOST = ConfigUtil.getString("esign.scca.apiHosts");

    public static final String CERT_TYPE_PERSONAL = "Personal";
    public static final String CERT_TYPE_ORGANIZATIONAL = "Organizational";

    public static final String DEF_PASSWORD = "P123456";


    /**
     * 云证书-申请
     */
    private static final String CLOUD_CERT_ENROLL = API_HOST + "/api/cloudCert/open/cert/certEnroll";

    /**
     * 云证书-变更信息
     */
    private static final String CLOUD_CERT_CHANGE = API_HOST + "/api/cloudCert/open/cert/certChange";

    /**
     * 云证书-续期
     */
    private static final String CLOUD_CERT_RENEW = API_HOST + "/api/cloudCert/open/cert/certRenew";

    /**
     * 云证书-吊销
     */
    private static final String CLOUD_CERT_REVOKE = API_HOST + "/api/cloudCert/open/cert/certRevoke";

    /**
     * 云证书PIN码-变更
     */
    private static final String CLOUD_CERT_PIN_CHANGE = API_HOST + "/api/cloudCert/open/cert/changePin";

    /**
     * 云证书PIN码-重置
     */
    private static final String CLOUD_CERT_PIN_RESET = API_HOST + "/api/cloudCert/open/cert/resetPin";

    /**
     * 云证书-签名（证书过期后无法签名）
     */
    private static final String CLOUD_CERT_SIGN = API_HOST + "/api/cloudCert/open/cert/sign";

    /**
     * 云证书-签名扩展（证书自动续期）
     */
    private static final String CLOUD_CERT_SIGN_EXT = API_HOST + "/api/cloudCert/open/cert/signExt";

    /**
     * 获取场景二维码
     */
    private static final String GENERATE_QRCODE = API_HOST + "/api/appManager/qrcode/generateQRCode";

    /**
     * 云证书申请
     *
     * @param certInfoVO
     * @return
     * @throws BusinessException
     */
    public static SccaCertVO cloudCertEnroll(SccaCertInfoVO certInfoVO) throws BusinessException {
        SccaResponseVO<SccaCertVO> responseVO = post(CLOUD_CERT_ENROLL, certInfoVO, new ParameterizedTypeReference<SccaResponseVO<SccaCertVO>>() {
        });
        if (!responseVO.getSuccess()) {
            // 已注册的账户执行修改
            if (responseVO.getResultMsg().contains("证书申请与首次提交不一致") || responseVO.getResultMsg().contains("PIN校验失败")) {
                log.info("四川CA-云证书申请证书已存在执行修改 certInfoVO:{}", certInfoVO);
                cloudCertPinReset(certInfoVO.getEntityId(), certInfoVO.getPin());
                return cloudCertChange(certInfoVO);
            }
            log.warn("四川CA-云证书申请失败 response:{}, param:{}", responseVO, certInfoVO);
            throw new BusinessException(ExceptionCodes.ESIGN_USER_SYNC_FAILED, "SCCA:" + responseVO.getResultMsg());
        }
        return responseVO.getBody();
    }


    /**
     * 证书变更
     *
     * @param certInfoVO
     * @return
     * @throws BusinessException
     */
    public static SccaCertVO cloudCertChange(SccaCertInfoVO certInfoVO) throws BusinessException {
        SccaResponseVO<SccaCertVO> responseVO = post(CLOUD_CERT_CHANGE, certInfoVO, new ParameterizedTypeReference<SccaResponseVO<SccaCertVO>>() {
        });
        if (!responseVO.getSuccess()) {
            log.warn("四川CA-云证书修改失败 response:{}, param:{}", responseVO, certInfoVO);
            throw new BusinessException(ExceptionCodes.ESIGN_USER_SYNC_FAILED, "SCCA:" + responseVO.getResultMsg());
        }
        return responseVO.getBody();
    }

    /**
     * 证书续签
     *
     * @param entityId
     * @param pin
     * @return
     * @throws BusinessException
     */
    public static SccaCertVO cloudCertRenew(String entityId, String pin) throws BusinessException {
        Map<String, String> param = Maps.newHashMap();
        param.put("entityId", entityId);
        param.put("pin", pin);
        SccaResponseVO<SccaCertVO> responseVO = post(CLOUD_CERT_RENEW, param, new ParameterizedTypeReference<SccaResponseVO<SccaCertVO>>() {
        });
        if (!responseVO.getSuccess()) {
            log.warn("四川CA-云证书续期失败 response:{}, param:{}", responseVO, param);
            throw new BusinessException(ExceptionCodes.ESIGN_USER_SYNC_FAILED, responseVO.getResultMsg());
        }
        return responseVO.getBody();
    }

    /**
     * 证书PIN修改（需要原PIN）
     *
     * @param entityId
     * @param pin
     * @param newPin
     * @return
     * @throws BusinessException
     */
    public static void cloudCertPinChange(String entityId, String pin, String newPin) throws BusinessException {
        Map<String, String> param = Maps.newHashMap();
        param.put("entityId", entityId);
        param.put("pin", pin);
        param.put("newPin", newPin);
        SccaResponseVO<String> responseVO = post(CLOUD_CERT_PIN_CHANGE, param, new ParameterizedTypeReference<SccaResponseVO<String>>() {
        });
        if (!responseVO.getSuccess()) {
            log.warn("四川CA-云证书PIN码修改失败 response:{}, param:{}", responseVO, param);
            throw new BusinessException(ExceptionCodes.ESIGN_USER_SYNC_FAILED);
        }
    }

    /**
     * 证书PIN重置（无需原PIN）
     *
     * @param entityId
     * @param pin
     * @return
     * @throws BusinessException
     */
    public static void cloudCertPinReset(String entityId, String pin) throws BusinessException {
        Map<String, String> param = Maps.newHashMap();
        param.put("entityId", entityId);
        param.put("pin", pin);
        SccaResponseVO<String> responseVO = post(CLOUD_CERT_PIN_RESET, param, new ParameterizedTypeReference<SccaResponseVO<String>>() {
        });
        if (!responseVO.getSuccess()) {
            log.warn("四川CA-云证书PIN码重置失败 response:{}, param:{}", responseVO, param);
            throw new BusinessException(ExceptionCodes.ESIGN_USER_SYNC_FAILED, "SCCA:" + responseVO.getResultMsg());
        }
    }

    /**
     * 证书作废
     *
     * @param entityId
     * @param pin
     * @throws BusinessException
     */
    public static void cloudCertRevoke(String entityId, String pin) throws BusinessException {
        Map<String, String> param = Maps.newHashMap();
        param.put("entityId", entityId);
        param.put("pin", pin);
        SccaResponseVO<String> responseVO = post(CLOUD_CERT_REVOKE, param, new ParameterizedTypeReference<SccaResponseVO<String>>() {
        });
        if (!responseVO.getSuccess()) {
            log.warn("四川CA-云证书吊销失败 response:{}, param:{}", responseVO, param);
            throw new BusinessException(ExceptionCodes.ESIGN_USER_SYNC_FAILED, "SCCA:" + responseVO.getResultMsg());
        }
    }

    /**
     * 云证书签名（证书过期后无法签名）
     *
     * @param entityId
     * @param pin
     * @param toSign
     * @return
     * @throws BusinessException
     */
    public static SccaCertSignVO cloudCertSign(String entityId, String pin, String toSign) throws BusinessException {
        Map<String, String> param = Maps.newHashMap();
        param.put("entityId", entityId);
        param.put("pin", pin);
        param.put("toSign", toSign);
        SccaResponseVO<SccaCertSignVO> responseVO = post(CLOUD_CERT_SIGN, param, new ParameterizedTypeReference<SccaResponseVO<SccaCertSignVO>>() {
        });

        if (!responseVO.getSuccess()) {
            String msg = responseVO.getResultMsg();
            if ("PIN校验失败".equals(msg)) {
                msg = "签名密码错误请重新输入";
            } else {
                msg = "SCCA:" + msg;
            }
            log.warn("四川CA-云证书签名失败 response:{}, param:{}", responseVO, param);
            throw new BusinessException(ExceptionCodes.ESIGN_SIGN_FIALED, msg);
        }
        return responseVO.getBody();
    }

    /**
     * 云证书扩展签名（证书自动续期）
     *
     * @param certInfoVO
     * @param toSign
     * @return
     * @throws BusinessException
     */
    public static SccaCertSignVO cloudCertSignExt(SccaCertInfoVO certInfoVO, String toSign) throws BusinessException {
        Map<String, Object> param = BeanUtil.beanToMap(certInfoVO);
        param.put("toSign", toSign);
        SccaResponseVO<SccaCertSignVO> responseVO = post(CLOUD_CERT_SIGN_EXT, param,
            new ParameterizedTypeReference<SccaResponseVO<SccaCertSignVO>>() {
            });
        if (!responseVO.getSuccess()) {
            String msg = responseVO.getResultMsg();
            if ("PIN校验失败".equals(msg)) {
                msg = "签名密码错误请重新输入";
            } else {
                msg = "SCCA:" + msg;
            }
            log.warn("四川CA-云证书扩展签名失败 response:{}, param:{}", responseVO, param);
            throw new BusinessException(ExceptionCodes.ESIGN_SIGN_FIALED, msg);
        }
        return responseVO.getBody();
    }

    /**
     * 获取场景二维码（自检使用）
     *
     * @param envCode  场景代码
     * @param extParam 扩展参数(扩展参数在业务执行成功后, 会原样传回调用系统) 非必填
     * @throws BusinessException
     */
    public static String generateQRCode(String envCode, String extParam) throws BusinessException {
        Map param = Maps.newHashMap();
        param.put("envCode", envCode);
        param.put("extParam", extParam);

        Map<String, String> headers = Maps.newHashMap();
        headers.put("app_id", APP_ID);
        headers.put("signature", getSignature(param));

        String body =
                HttpUtil.createPost(GENERATE_QRCODE).timeout(2000).form(param).addHeaders(headers).execute().body();
        SccaResponseVO<GenerateQRCodeVO> responseVO = null;
        if (JSONUtil.isJson(body)) {
            responseVO = JSONUtil.toBean(body, SccaResponseVO.class);
        }
        if (responseVO == null) {
            log.warn("四川CA-获取场景二维码失败 response:{}, param:{}", responseVO, param);
            throw new BusinessException(ExceptionCodes.ESIGN_USER_SYNC_FAILED);
        }
        return responseVO.getResultMsg();
    }

    // 创建签章配置

    // 获取签章图片

    // 删除签章图片

    // 删除签章配置

    /**
     * 接口请求签名后以post发送
     *
     * @param url
     * @param paramMap
     * @param responseTypeReference
     * @param <T>
     * @return
     */
    public static <T, E> T post(String url, E paramBean, ParameterizedTypeReference<T> responseTypeReference) throws BusinessException {
        Map<String, Object> paramMap = null;
        if (!(paramBean instanceof Map)) {
            paramMap = BeanUtil.beanToMap(paramBean);
        } else {
            paramMap = (Map) paramBean;
        }

        Map<String, String> headers = Maps.newHashMap();
        headers.put("app_id", APP_ID);
        headers.put("signature", getSignature(paramMap));

        log.info("四川CA接口请求发起 url:{}, headers:{}, param:{}", url, headers, paramMap);
        ResponseEntity<T> responseEntity = RestHttpUtil.post().header(headers).paramMap(paramMap).url(url).execute(responseTypeReference);
        if (!responseEntity.getStatusCode().is2xxSuccessful()) {
            log.warn("四川CA接口访问失败 statusCode:{}, url:{}, body:{}", responseEntity.getStatusCode(), url, responseEntity.getBody());
            throw new BusinessException(ExceptionCodes.HTTP_ERR);
        }
        log.info("四川CA接口请求响应 url:{}, status:{}, body:{}", url, responseEntity.getStatusCodeValue(), responseEntity.getBody());
        return responseEntity.getBody();
    }

    /**
     * 获取请求参数签名
     *
     * @param paramMap
     * @return
     */
    public static String getSignature(Map<String, Object> paramMap) {

        List<String> paramsKeyList = new ArrayList<>(paramMap.keySet());
        Collections.sort(paramsKeyList);

        StringBuilder builder = new StringBuilder();
        paramsKeyList.forEach(k -> {
            Object v = paramMap.get(k);
            if (v != null) {
                builder.append(v).append("&");
            }
        });
        builder.deleteCharAt(builder.length() - 1);
        return hmacSha1Hex(builder.toString(), APP_SECRET);
    }

    /**
     * Returns the digest for the input data.
     *
     * @param valueToDigest
     * @param key
     * @return
     */
    private static String hmacSha1Hex(String valueToDigest, String key) {
        return new HmacUtils(HmacAlgorithms.HMAC_SHA_1, key).hmacHex(valueToDigest);
    }

    @NoArgsConstructor
    @Data
    static class SccaResponseVO<T> {

        public static final Integer SUCCESS_CODE = 0;

        @JsonProperty("result_code")
        private Integer resultCode;

        @JsonProperty("result_msg")
        private String resultMsg;

        private Boolean success;

        private T body;

        @Override
        public String toString() {
            return JSON.toJSONString(this);
        }
    }
}
